Keyword: coroutine
這幾天在使用網路功能時,都使用到了Kotlin的Coroutine,在撰寫KMM乃至於大部分的Kotlin移動端應用,都有Coroutine的身影.
這邊的介紹是簡略版的,主要是提到如何去使用,在KMM的專案中會不避免地遇到許多Coroutine的使用.真的要講的話可以獨立開一個鐵人賽項目.
剛好我們團員這次有針對Coroutine進行鐵人賽!內容非常詳盡!如果想要暸解更多,歡迎訂閱
Coroutine 停看聽 :: 2021 iThome 鐵人賽
Coroutine是一個自創詞,由代表共同的Co與工作常規的routine兩個詞合併起來,在wiki上面被翻作"共常",但是我更喜歡大陸普遍的說法,“協程”.描述了Coroutine讓不同工作項共同運作的特色.
在Kotlin的Coroutine中,當某個函式正在等待某個事件發生的時候,可能是等待網路請求的結果,可能是等待使用者的回應或操作,甚至可能是印表機印速度太慢,導致Cpu無事可幹.白白浪費掉這些效能,所以就有人想到,我們能不能讓等待事件結果的函式,先把目前的狀態儲存起來,解放這些寶貴的資源,先給現在正需要資源的其他人,等到等待的事件發生了,再從暫存狀態復原回來,繼續執行.這就是Coroutine環境的特色
Kotlin的Coroutine,和一般常說Coroutine上有些許差別,由於Kotlin是跑在JVM上的,而嚴格來說,JVM沒有提供真正的Coroutine環境,因此實際上還是用Thread去模擬Coroutine環境.在與其他語言的使用者交流時可能需要注意到這點.
另外,Kotlin正在開發其他語言的版本,如果在其他語言平台,且有支援Coroutine環境,那時就能真正使用到Coroutine,而不是JVM上以Thread模擬的Coroutine環境.
interface CafeApi {
suspend fun fetchCafeFromApi(city:String): List<CafeResponseItem>
}
這是前幾天寫在shared內的一個suspend 函式,suspend函式就有如上面介紹的一樣,具有暫時交出控制權並且暫存目前狀況的能力,因為這是一個Ktor的網路請求,所以需要等待發生的事件,就在發出網路請求後,一直到網路請求的回應.如果使用普通的Thread,在這段時間內Thread都是浪費的.所以把這個function標註為suspend,讓Kotlin減少浪費的資源與效能.
我們在Android內部使用Coroutine的時候,就會像
class MainViewModel : ViewModel() {
...
fun fetchCafeData(city: String = "") {
viewModelScope.launch() {
val result = async { dataRepository.fetchCafesFromNetwork(city) }
cafeList.value = result.await()
}
}
...
}
我們外面使用了一個scope包裹了所有的suspend函式,由於suspend函式可以回復的特性,必須運行在一個coroutineScope中,不然需要回復狀態時,suspend函式不知道自己該回到哪邊.
而scope有許多種,畫面層使用的lifecycleScope,ViewModel曾使用的viewModelScope,以及全局共用的globalScope等等.這次就是用跟ViewModel生命週期綁定的viewModelScope
由於這次是拉取網路資料,因此這個fetchCafesFromNetwork將會有回傳值,我們使用async關鍵字將這個部分包裹起來,並且將回傳的部分用一個result參數代表.
當我們要使用到其中的數據時,使用await()呼叫,這樣在結果回來前,這段的coroutine都會被suspend,直到有結果回傳.
寫一塊來驗證看看
class MainViewModel : ViewModel() {
...
fun checkAsync() {
viewModelScope.launch() {
val result1 = async { delaySuspendFunction1() }
val result2 = async { delaySuspendFunction2() }
println("job1 : "+result1.await()+ " at" + System.currentTimeMillis())
println("job2 : "+result2.await()+ " at" + System.currentTimeMillis())
}
}
private suspend fun delaySuspendFunction1() {
withContext(Dispatchers.IO){
delay(1000L)
"result1 "
}
}
private suspend fun delaySuspendFunction2() {
withContext(Dispatchers.IO){
delay(10L)
"result2 "
}
}
...
}
可以看到在job1印出來後,幾乎馬上job2的文字就出現了,因為job2早就執行完了,在等待job1的await回傳,可以把兩行的順序交換,看看有什麼結果.
今天先大概介紹到這邊,明天會來講Coroutine 的一些注意點